// Copyright (c) 2015, The mangorippers. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// The golang stdlib is used here and is licensed under the 3-clause New BSD.

// mangoripper
// A concurrent manga ripper written in golang.
// https://code.google.com/p/mangoripper/
package main

import (
	"archive/zip"
	"bytes"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"

	"code.google.com/p/mangoripper/mangastream_com"
	"code.google.com/p/mangoripper/mangapanda_com"
)

type image struct {
	url string
	img []byte
}

type release struct {
	prefix string
	suffix string
	imgs   []*image
}

// asyncDownload takes a pointer to an image struct and downloads .url into .img.
// the image pointer is then sent back via a channel.
func (img *image) asyncDownload(client *http.Client, ch chan *image) {
	log.Println("Downloading " + img.url + ".")
	req, err := http.NewRequest("GET", img.url, nil)
	if err != nil {
		log.Fatal(err)
	}
	req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:31.0) Gecko/20100101 Firefox/31.0 Iceweasel/31.6.0")

	res, err := client.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	if res.StatusCode < 200 || res.StatusCode > 299 {
		log.Fatal("Server returned error status code: ", res.StatusCode)
	}
	img.img, err = ioutil.ReadAll(res.Body)
	if err != nil {
		log.Fatal(err)
	}
	res.Body.Close()
	ch <- img
}

// asyncDownload inits a goroutine for each image found in the release struct.
// it then waits for the downloading to finish and writes the data to the struct.
func (rls *release) asyncDownload() {
	tr := &http.Transport{
		DisableKeepAlives: true,
	}
	client := &http.Client{Transport: tr}
	ch := make(chan *image)
	for _, img := range rls.imgs {
		go img.asyncDownload(client, ch)
	}

	for i := 0; len(rls.imgs) != i; {
		select {
		case r := <-ch:
			i++
			for _, img := range rls.imgs {
				if img.url == r.url {
					img.img = r.img
					log.Println(img.url + " complete.")
				}
			}
		default:
			time.Sleep(100 * time.Millisecond)
		}
	}
}

// newImg creates a new image struct of a url and appends it's pointer in the release struct.
func (rls *release) newImg(url string) {
	img := &image{}
	img.url = url
	rls.imgs = append(rls.imgs, img)
}

// compress takes the release struct and writes out .cbz(zip) out of it.
func (rls *release) compress() {
	buf := &bytes.Buffer{}
	w := zip.NewWriter(buf)
	for i, img := range rls.imgs {
		nameSplit := strings.Split(strings.Split(img.url, "/")[len(strings.Split(img.url, "/"))-1], ".")
		numStr := strconv.Itoa(i)
		for len(numStr) <= 2 {
			numStr = "0" + numStr
		}
		img.url = rls.prefix + "_" + numStr + "_" + rls.suffix + "[" + nameSplit[0] + "]." + nameSplit[1]

		f, err := w.Create(img.url)
		if err != nil {
			log.Fatal(err)
		}
		_, err = f.Write(img.img)
		if err != nil {
			log.Fatal(err)
		}
	}
	err := w.Close()
	if err != nil {
		log.Fatal(err)
	}
	ioutil.WriteFile(rls.prefix+"_"+rls.suffix+".cbz", buf.Bytes(), 0777)
}

// walk walks through the release starting with the first url filling the struct with the images's urls.
// TODO: maybe there's a pretty way to use reflect for a more modular design...
func (rls *release) walk(url string) {
	switch strings.Split(url, "/")[2] {
	default:
		log.Fatal("Walking failed. Domain unsupported.")
	case "mangastream.com", "readms.com", "www.mangastream.com", "www.readms.com":
		{
			var img string
			for num, oldNum := 0, 0; num != -1 && num >= oldNum; num, oldNum = mangastream_com.GrokUrl(url), num {
				log.Println("Walking " + url + ".")
				url, img = mangastream_com.Step(url)
				rls.newImg(img)
			}
		}
	case "mangapanda.com", "www.mangapanda.com":
		{
			var img string
			for num, oldNum := 0, 0; num != -1 && num >= oldNum; num, oldNum = mangapanda_com.GrokUrl(url), num {
				log.Println("Walking " + url + ".")
				url, img = mangapanda_com.Step(url)
				rls.newImg(img)
			}
		}
	}
}

func main() {
	log.Println("\nmangoripper: A concurrent manga ripper written in golang.\n")
	if len(os.Args) < 4 {
		log.Fatal("Arguments missing\nPlease pass a prefix, suffix and url.\ne.g. \"mangoripper Bleach_545 Tite_Kubo http://readms.com/read/bleach/545/1978/1\" will create \"Bleach_545_Tite_Kubo.cbz\".")
	}

	rls := &release{}
	rls.prefix = os.Args[1]
	rls.suffix = os.Args[2]
	log.Println("Walking the release.")
	rls.walk(os.Args[3])
	log.Println("Downloading the release.")
	rls.asyncDownload()
	log.Println("Compressing the release.")
	rls.compress()
	log.Println("Done.")
}
